Function.RegisterNamespace("System.Script.Attributes");

System.Script.Attributes.DecoratedFunction=function(){
    // Private members
    var _attributes;
    var _decoration;
    var _method;
    var _name;
    
    //Public members
    this.Name;
    
    // ctor
    function DecoratedFunction(method,name,attributes1,attributes2,attributesN){
        if(this.constructor!=System.Script.Attributes.DecoratedFunction)method=function(){};
        if(!Object.IsType(Function,method))throw new Error("System.Script.Attributes.DecoratedFunction.ctor: 'method' must be a valid Function pointer.");
        _attributes=[];
        _decoration=this;
        _method=method;
        _name=name||Function.GetName(method);
        this.Name=_name;
        Array.ForEach(Array.prototype.slice.call(arguments,2),addAttributeArgument,null,{Target:_attributes});
        DecoratedFunction.Decoration=DecorationWrapper;
        processAttributes();
    }
    
    // Decorated ctor
    function DecorationWrapper(){
        var context=Array.ForEach(_attributes,beforeInvokeAttribute);
        if(context.Cancel)return null;
        try{
            return _method.apply(_decoration,arguments);
        }catch(e){
            context.Error=e;
        }finally{
            Array.ForEach(_attributes,afterInvokeAttribute,null,context);
            if(context.Error)throw context.Error;
        }
    }

    DecorationWrapper.GetDecoration=function(){
        return _decoration;
    };
    
    // Public Methods
    this.GetAttributes=function(match){
        if(match==undefined)return _attributes.slice(0);
        if(!Object.IsType(Function,match.toString))throw new Error("System.Script.Attributes.DecoratedFunction.GetAttributes: 'match' must be convertible to String.");
        var context=Array.ForEach(_attributes,addAttribute,attributeMatchPredicate,{Match:match,Target:[]});
        return context.Target;
    };
    
    this.GetMethod=function(){
        return _method;
    };
    
    // ctor invocation
    DecoratedFunction.apply(this,arguments);
    return DecorationWrapper;

    // Private methods
    function addAttribute(attribute,context){
        context.Target.push(attribute);
    }

    function addAttributeArgument(attributeArgument,context){
        if(Object.IsType(Array,attributeArgument))addAttributeCluster(attributeArgument,context);
        else addAttribute(attributeArgument,context);
    }

    function addAttributeCluster(attributeCluster,context){
        Array.ForEach(attributeCluster,addAttribute,null,{Target:context.Target});
    }

    function afterInvokeAttribute(attribute,context){
        invokeAttribute(attribute,context,"AfterInvoke");
    }

    function beforeInvokeAttribute(attribute,context){
        invokeAttribute(attribute,context,"BeforeInvoke");
    }
    
    function invokeAttribute(attribute,context,method){
        if(!Object.IsType(Function,attribute&&attribute[method]))return;
        if(attribute[method]==System.Script.Attributes.Attribute[method])return;
        attribute[method](context);
    }

    function processAttributes(){
        for(var i=0;i<_attributes.length;i++){
            if(!Object.IsType(Function,_attributes[i]))continue;
            try{
                // ECMA Compliance fix (.caller is host-specific)
                if(!processAttributes.caller)_attributes[i].prototype.Target=DecorationWrapper;
                _attributes[i]=new _attributes[i]();
            }catch(e){
                throw new Error(String.Format("System.Script.Attributes.DecoratedFunction.processAttributes: unable to instantiate attribute '{0}' at position [{1}].\nError: {2}",_attributes[i].Name||Function.GetName(_attributes[i]),i,e));
            }
        }
    }
    
    // Predicates    
    function attributeMatchPredicate(attribute,context){
        if(!attribute)return false;
        var match=context.Match;
        if(Object.IsType(Function,match))return attribute==match||attribute.constructor==match;
        match=match.Name||match.toString();
        return attribute.Name==match||Function.GetName(attribute.constructor)==match;
    }
};

System.Script.Attributes.DecoratedFunction.Implement(System.Script.Attributes.IDecoration,"System.Script.Attributes.DecoratedFunction");